package com.spbl.apache.shiro.config;

import com.spbl.apache.shiro.config.session.CustomeSessionListener;
import com.spbl.common.ConstantUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Description(配置 securityManager shiroFilter
 * 参考 https://blog.csdn.net/qq_34021712/article/details/80418112
 *     http://shiro.apache.org/session-management.html#SessionManagement-sessionTimeout
 * )
 * author: Gao xueyong
 * Create at: 2020/6/8 10:25 下午
 */
@Slf4j
@Configuration
public class ShiroConfig {

    @Bean
    public  CustomRealm getCustomRealm(){
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        customRealm.setCacheManager(cacheManager());
        return customRealm;
    }
    /**
     * 配置shiroFilter
     * @return
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
//        没有登录的用户请求需要登录的页面时自动跳转到登录页面
        shiroFilterFactoryBean.setLoginUrl("/login.html");
//        登录成功默认跳转页面，不配置则跳转至”/”，可以不配置，直接通过代码进行处理。
        shiroFilterFactoryBean.setSuccessUrl("/hello");
//        没有权限默认跳转的页面，登录的用户访问了没有被授权的资源自动跳转到的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");

        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        //配置自定义登出 覆盖 logout 之前默认的LogoutFilter
        filtersMap.put("logout", shiroLogoutFilter());

        shiroFilterFactoryBean.setFilters(filtersMap);


        //拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//        配置不会被拦截的资源 顺序判断
//        anon 表示放行
//        authc 需要身份认证
//        logout 注销执行后会跳转至 登录页面
//        roles[admin] 参数可写多个，表示某个或某些角色才能通过。多个参数时，可写作roles[admin,user]
//        perms[admin] 参数可写多个，表示拥有某个或某些权限时才能通过，如perms[user:create]

        filterChainDefinitionMap.put("/static/js/**", "anon");
        filterChainDefinitionMap.put("/static/css/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/login.html", "anon");
//        以 /user/admin  开头的用户需要身份认证，
        filterChainDefinitionMap.put("/user/admin*", "authc");
//        以 /user/student 开头的用户需要角色认证，拥有“admin”角色才允许访问
        filterChainDefinitionMap.put("/user/student*/**", "roles[admin]");
//        以 /user/create  开头的用户需要权限认证，是“user:create”才允许
        filterChainDefinitionMap.put("/user/create*/**", "perms[\"user:create\"]");
        filterChainDefinitionMap.put("/logout", "logout");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//        自定义拦截器
        return shiroFilterFactoryBean;
    }

    /**
     * 配置session过期时间
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 设置session过期时间3600s  -1为永不超时
        sessionManager.setGlobalSessionTimeout(ConstantUtil.sessionTimeOut);
//        是否开启删除无效的session对象  默认为true
        sessionManager.setDeleteInvalidSessions(true);
//        是否开启定时调度器进行检测过期session 默认为true
//        sessionManager.setSessionValidationSchedulerEnabled(true);
//        是否开启定时调度器进行检测过期session 默认为true
//        sessionManager.setSessionValidationSchedulerEnabled(true);
//        设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        sessionManager.setSessionValidationInterval(ConstantUtil.sessionValidationInterval);
//        取消url 后面的 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
//        shiro 的session默认放在cookie中 禁用
        sessionManager.setSessionIdCookieEnabled(true);

        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        //配置监听
        listeners.add(sessionListener());
        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionIdCookie(sessionIdCookie());
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setCacheManager(cacheManager());
        return sessionManager;
    }

    @Bean
    public CacheManager cacheManager() {
        MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用内存缓存
        return cacheManager;
    }
    /**
     * 配置 securityManager
     *
     * @return
     */
    @Bean
    public SessionsSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
        securityManager.setRealm(getCustomRealm());
        return securityManager;

    }

    /**
     * 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),
     * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 凭证匹配器  加密
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName(ConstantUtil.hashAlgorithmName);//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(ConstantUtil.hashIterations);//散列的次数，比如散列两次，相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    /**
     * 不能用@bean做为bean 会导致重定向过多
     * 可以参考https://blog.csdn.net/genaro26/article/details/91444469
     * @return
     */
    public ShiroLogoutFilter shiroLogoutFilter(){
        ShiroLogoutFilter shiroLogoutFilter = new ShiroLogoutFilter();
        //配置登出后重定向的地址，
        shiroLogoutFilter.setRedirectUrl("/login.html");
        return shiroLogoutFilter;
    }

    /**
     * 持久化session
     * @return
     */
    @Bean
    public SessionDAO sessionDAO() {
        EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO();
        //使用ehCacheManager
        enterpriseCacheSessionDAO.setCacheManager(cacheManager());
        //设置session缓存的名字 默认为 shiro-activeSessionCache 详见 ehcache.xml
        enterpriseCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        //sessionId生成器
        enterpriseCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        return enterpriseCacheSessionDAO;
    }

    /**
     * 默认生产会话id
     * @return
     */
    @Bean
    public SessionIdGenerator sessionIdGenerator() {
        return new JavaUuidSessionIdGenerator();
    }

    /**
     * 配置session监听
     * @return
     */
    @Bean("sessionListener")
    public CustomeSessionListener sessionListener(){
        CustomeSessionListener sessionListener = new CustomeSessionListener();
        return sessionListener;
    }
    @Bean("sessionIdCookie")
    public SimpleCookie sessionIdCookie(){
        //这个参数是cookie的名称
        SimpleCookie simpleCookie = new SimpleCookie("sid");
        //setcookie的httponly属性如果设为true的话，会增加对xss防护的安全系数。它有以下特点：

        //setcookie()的第七个参数
        //设为true后，只能通过http访问，javascript无法访问
        //防止xss读取cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //maxAge=-1表示浏览器关闭时失效此Cookie
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }
}